package allod.som

import initializer.{LatticeSerializer, Initializer}
import input.InputSource
import Lattice.Pattern
import Lattice.Neuron
import Lattice.NeuronLocation
import scala.Array
import xml.Node

/**
 * Created by IntelliJ IDEA.
 * User: allod
 * Date: 1 січ 2010
 * Time: 15:41:49
 */

class Lattice(val columns: Int, val rows: Int, initializer: Initializer) {
  def this(node: Node) {
    this ((node \\ "columns").text.toInt, (node \\ "rows").text.toInt,
      new LatticeSerializer(node))
  }

  val neuronsCount = columns * rows

  val inputDimension = initializer.inputDimension()

  val metric = initializer.metric

  protected val neurons = createNeurons(initializer)

  def neuronAt(index: NeuronLocation) = neurons(index._1)(index._2)

  def neuronAt(row: Int, column: Int) = neurons(row)(column)

  def distanceToPattern(index: NeuronLocation, input: Pattern) =
    metric.distance(input, neurons(index._1)(index._2))

  def distanceToNeuron(first: NeuronLocation, second: NeuronLocation) =
    metric.distance(neurons(first._1)(first._2), neurons(second._1)(second._2))

  def closestNeuron(input: Pattern): NeuronLocation = metric.minDistance(input, this)

  def closestPattern(index: NeuronLocation, patterns: InputSource): Pattern = {
    var minValue = distanceToPattern(index, patterns.elements(0))
    var mostCorrespondingPattern = patterns.elements(0)
    patterns.elements.foreach(p => {
      if (minValue > distanceToPattern(index, p)) {
        minValue = distanceToPattern(index, p)
        mostCorrespondingPattern = p
      }
    })
    mostCorrespondingPattern
  }

  def toXML(): Node =
    <lattice>
      <columns>{columns}</columns>
      <rows>{rows}</rows>
      <inputDimension>{inputDimension}</inputDimension>
      <metric>{metric.getClass.getCanonicalName}</metric>
      <neurons>
        {neuronsToXML()}
      </neurons>
    </lattice>

  private def neuronsToXML() =
    for{
      column <- 0 until columns
      row <- 0 until rows
      val neuron = neurons(column)(row)
    } yield
       <neuron>
        <column>{column}</column>
        <row>{row}</row>
        <weights>
          {neuron map (w => <value>{w}</value>)}
        </weights>
      </neuron>

  private def createNeurons(init: Initializer): Array[Array[Neuron]] = {
    val result = new Array[Array[Neuron]](columns, rows)
    for{
      i <- 0 until columns
      j <- 0 until rows
    } result(i)(j) = init.createNeuron(i, j)
    result
  }
}

object Lattice {
  type Pattern = List[Double]
  type Neuron = List[Double]
  type NeuronLocation = Tuple2[Int, Int]

  object Builder {
    implicit def builderToLattice(builder: Builder) = builder.peer
  }

  class Builder(val columns: Int, val rows: Int, initializer: Initializer) extends Initializer {
    private[this] var lattice = new Lattice(columns, rows, initializer)

    def peer = lattice

    def createNeuron(layerIndex: Int, neuronIndex: Int) = lattice.neurons(layerIndex)(neuronIndex)

    def metric = lattice.metric

    def inputDimension() = lattice.inputDimension

    def tuneNeuron(index: Tuple2[Int, int], newValues: List[Double]) {
      lattice.neurons(index._1)(index._2) = newValues
    }

    def build(): Lattice = {
      val result = lattice
      lattice = new Lattice(columns, rows, this)
      result
    }
  }
}